feat(run-environment): support CircleCI for GitHub repositories - #482
feat(run-environment): support CircleCI for GitHub repositories#482fargito wants to merge 3 commits into
Conversation
Greptile SummaryThe PR adds CircleCI support for GitHub-hosted repositories and updates the upload metadata contract.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; the earlier compatibility-probe issue is fixed by exercising the actual OIDC mint command before benchmark execution.
|
| Filename | Overview |
|---|---|
| src/run_environment/circleci/oidc.rs | Implements custom-audience token minting and preserves CircleCI CLI failures without exposing successful token output. |
| src/run_environment/circleci/provider.rs | Implements CircleCI detection, GitHub repository validation, run metadata, parallel run parts, and preflight/per-upload OIDC handling. |
| src/run_environment/interfaces.rs | Adds the CircleCI environment variant and removes redundant provider-specific metadata fields. |
| src/upload/interfaces.rs | Advances the upload metadata schema version to 11. |
| src/upload/uploader.rs | Adds CircleCI-specific authentication guidance for rejected uploads. |
Sequence Diagram
sequenceDiagram
participant Job as CircleCI job
participant Runner as CodSpeed runner
participant CLI as CircleCI CLI
participant Backend as CodSpeed backend
Job->>Runner: Start benchmark command
Runner->>Runner: Detect CircleCI and build provider
Runner->>CLI: circleci run oidc get
CLI-->>Runner: Preflight token
Runner->>Runner: Execute benchmarks
loop Each upload
Runner->>CLI: Mint fresh OIDC token
CLI-->>Runner: Upload token
Runner->>Backend: Upload metadata and profile
end
Reviews (3): Last reviewed commit: "refactor(upload): drop ghData and glData..." | Re-trigger Greptile
Merging this PR will not alter performance
|
|
@greptileai review |
12d9f72 to
600b868
Compare
e2e325d to
dfa68d0
Compare
The build runs on CircleCI while commits and pull requests live on GitHub, so a
run is reported against the GitHub commit and pull request. Detection is
`CIRCLECI=true`, and the repository comes from `CIRCLE_REPOSITORY_URL`. CircleCI
also builds Bitbucket and GitLab repositories, so the remote's domain is checked
and anything but `github.com` is refused with an error naming the domain found.
That domain is the only signal available at runtime: `pipeline.project.type` is a
pipeline value, interpolated when the config is compiled, so it never reaches the
job as an environment variable.
`runId` is `CIRCLE_WORKFLOW_ID`, shared by every job and every parallel container
of a workflow; the per-job `CIRCLE_WORKFLOW_JOB_ID` would split one workflow into
unrelated runs. `runPartId` is `{CIRCLE_JOB}-{CIRCLE_NODE_INDEX}`, to stay unique
along both fan-out axes. No commit hash override is needed, as CircleCI checks
out the associated commit rather than a synthetic merge ref.
Uploads authenticate with an OIDC token the job mints for itself, so they need no
`CODSPEED_TOKEN` secret. The token CircleCI exposes in `CIRCLE_OIDC_TOKEN` and
`CIRCLE_OIDC_TOKEN_V2` cannot be used: its audience is fixed to the id of the
CircleCI organization, while CodSpeed requires its own. Only the `circleci` CLI
can request a custom audience — CircleCI publishes no endpoint for it, unlike the
`ACTIONS_ID_TOKEN_REQUEST_URL` GitHub Actions provides — so the runner shells out
to `circleci run oidc get`, once per upload, as a token expires an hour after it
is minted.
Whether a job can mint at all is settled by minting a token and throwing it away,
before the benchmarks run: a CLI can be installed and still predate
`run oidc get`, so probing for the binary would pass and the mint would fail once
the benchmarks had run for nothing. A static `CODSPEED_TOKEN` is used as is when
one is set, and stays required for a pull request opened from a fork, whose token
names the fork.
`baseRef` and `sender` are left empty: CircleCI exposes neither a base-branch
variable nor the id the repository provider gives the user who triggered a build.
Closes COD-2995
Closes COD-3257
…adata Both carried a run id and a job name that `runPart` already carries for every provider, so the payload named the same job twice, and only ever for GitHub Actions and GitLab CI. Every other provider filled both fields with `None` to say it was neither of them. GitHub Actions and GitLab CI keep the two values as plain fields read when the provider is built, the way CircleCI holds the ones its own run part needs. The payload no longer has the shape version 10 describes, so it becomes version 11. The backend accepts both: it reads either field only when `runPart` is missing, which no version since 6 has been. Refs COD-3257 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@greptileai review |
dfa68d0 to
21b4ee9
Compare
| if let Some(group_event) = get_group_event(record) { | ||
| match group_event { | ||
| GroupEvent::Start(name) | GroupEvent::StartOpened(name) => { | ||
| println!("{}", style(name).cyan().bold()); | ||
| } | ||
| GroupEvent::End => {} | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| if get_json_event(record).is_some() { | ||
| return; | ||
| } | ||
|
|
||
| if let Some(announcement) = get_announcement_event(record) { | ||
| println!("{}", style(announcement).green()); | ||
| return; | ||
| } | ||
|
|
||
| if level > self.log_level { | ||
| return; | ||
| } | ||
|
|
||
| match level { | ||
| Level::Error => { | ||
| println!("{}", style(message).red()); | ||
| } | ||
| Level::Warn => { | ||
| println!("{}", style(message).yellow()); | ||
| } | ||
| Level::Info => { | ||
| println!("{message}"); | ||
| } | ||
| Level::Debug => { | ||
| println!("{}", style(message).cyan()); | ||
| } | ||
| Level::Trace => { | ||
| println!("{}", style(message).magenta()); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Can't we do better for logging?
Add a CircleCI run environment, for repositories hosted on GitHub.
This replaces the former stack (#473 → #482) with a single PR against
main.Two commits: the CircleCI provider, and the metadata payload cleanup that came
with it.
The build runs on CircleCI while commits and pull requests live on GitHub, so
the run is reported against the GitHub commit and pull request. Detection is
CIRCLECI=true, and the repository comes fromCIRCLE_REPOSITORY_URL.CircleCI also builds Bitbucket and GitLab repositories, so the remote's domain
is checked and anything but
github.comis rejected with an error naming thedomain found. That domain is the only signal available at runtime:
pipeline.project.typeis a pipeline value, interpolated when the config iscompiled, so it never reaches the job as an environment variable.
Uploads authenticate with an OIDC token
The job mints its own token, so no
CODSPEED_TOKENsecret is needed. The tokenCircleCI exposes in
CIRCLE_OIDC_TOKENandCIRCLE_OIDC_TOKEN_V2cannot beused: its audience is fixed to the id of the CircleCI organization, while
CodSpeed requires its own. Only the
circleciCLI can request a custom audience— CircleCI publishes no endpoint for it, unlike the
ACTIONS_ID_TOKEN_REQUEST_URLGitHub Actions provides — so the runner shellsout to
circleci run oidc get, once per upload, as a token expires an hourafter it is minted.
Whether a job can mint at all is settled by minting a token and throwing it
away, before the benchmarks run. A CLI can be installed and still predate
run oidc get, so probing for the binary would pass and the mint would failonce the benchmarks had run for nothing.
Two cases keep needing a static token, and both are refused upfront: pull
requests opened from a fork, whose token names the fork, and jobs that cannot
mint at all. A third is only visible when the upload is refused: a pipeline
triggered by a custom webhook gets a token naming no repository.
The error messages link
integrations/ci/circleci/configuration, anchors#authenticationand#oidc-recommended. That page does not exist yet, andowes the three cases above.
Worth a look
than a synthetic merge ref, so reading git
HEADalready yieldsCIRCLE_SHA1.runIdisCIRCLE_WORKFLOW_ID, shared by every job and every parallelcontainer of a workflow. The per-job
CIRCLE_WORKFLOW_JOB_IDwould split oneworkflow into unrelated runs.
runPartIdis{CIRCLE_JOB}-{CIRCLE_NODE_INDEX}, to stay unique along both fan-out axes.ghDataandglDataare gone from the metadata, which makes itversion 11. Both carried a run id and a job name that
runParthas carriedfor every provider since version 6, so the payload named the same job twice,
and only ever for GitHub Actions and GitLab CI — every other provider filled
both with
Noneto say it was neither of them. The backend accepts bothshapes: it reads either field only when
runPartis missing.baseRefandsenderare left empty. CircleCI exposes neither abase-branch variable nor the id the repository provider gives the user who
triggered a build. The backend resolves both from the repository provider: the
base branch from the pull request, and the run's author from its commit.
Backend support is not released yet, so a real CircleCI job cannot be verified
end to end.
Closes COD-2995 Closes COD-3257